﻿using System.Collections.Generic;
using Core;

public partial class RedDotMgr 
{
    private static RedDotMgr ins;
    public static RedDotMgr Ins
    {
        get
        {
            if (ins == null)
                ins = new RedDotMgr();
            return ins;
        }
    }

    private Dictionary<RedDotType, RedDotLayer> layers = new Dictionary<RedDotType, RedDotLayer>();
    private Dictionary<RedDotType, HashSet<RedDotData>> dirtyDatas = new Dictionary<RedDotType, HashSet<RedDotData>>();
    private HashSet<RedDotData> needRemoves = new HashSet<RedDotData>();
    private HashSet<RedDotData> needUpdates = new HashSet<RedDotData>();

    private bool isInit;

    // 延迟更新时间
    private int delayTime = 200;
    private ITimeUnit delayTimeUnit;

    public void Init()
    {
        if (isInit)
        {
            Reset();
        }
        else
        {
            CreateTree();
        }
        isInit = true;  
    }

    public T GetChecker<T>(RedDotType redDotType)
    {
        if (layers.TryGetValue(redDotType, out var unit))
        {
            return (T)unit.RedDotChecker;
        }

        return default;
    }

    public void BindRedDot(RedDot redDot, RedDotType redDotType, object subType, object parentSubType, RedDotBindType bindType)
    {
        UnbindRedDot(redDot);

        redDot.redDotType = redDotType;
        redDot.subType = subType;
        redDot.parentSubType = parentSubType;

        if (layers.TryGetValue(redDotType, out var layer))
        {
            if (!layer.TryGetData(subType, parentSubType, out var data))
            {
                data = new RedDotData();
                data.redDotType = redDotType;
                data.bingType = bindType;
                data.subType = subType;
                data.parentType = parentSubType;
                layer.AddData(data);
            }

            data.redDots.Add(redDot);
            if (data.isDirty)
            {
                SetDirty(redDotType, subType, parentSubType);
            }
            else
            {
                redDot.ChangeRedState(layer.IsRed(data.subType, data.parentType));
            }
        }
        else
        {
            redDot.ChangeRedState(false);
        }
    }

    public void UnbindRedDot(RedDot redDot)
    {
        if (redDot == null || redDot.redDotType == RedDotType.None)
            return;

        if (layers.TryGetValue(redDot.redDotType, out var unit))
        {
            if (unit.TryGetData(redDot.subType, redDot.parentSubType, out var data))
            {
                data.redDots.Remove(redDot);
            }
        }
    }

    private RedDotLayer CreateRedDotLayer<CheckerType>(RedDotType redDotType) where CheckerType : IRedDotChecker, new()
    {
        var unit = new RedDotLayer();
        unit.RedDotType = redDotType;
        unit.RedDotChecker = new CheckerType();
        unit.RedDotChecker.Init();

        layers.Add(redDotType, unit);

        return unit;
    }

    public void SetDirty(RedDotType redDotType, object subType = null, object parentType = null)
    {
        if (!dirtyDatas.TryGetValue(redDotType, out var subTypes))
        {
            subTypes = new HashSet<RedDotData>();
            dirtyDatas[redDotType] = subTypes;
        }

        if (layers.TryGetValue(redDotType, out var layer))
        {
            if (layer.TryGetData(subType, parentType, out var data))
            {
                data.isDirty = true;
                subTypes.Add(data);
            }
        }

        CreateUpdateTime();
    }

    public void SetAllUnitDirty(RedDotType redDotType)
    {
        if (!dirtyDatas.TryGetValue(redDotType, out var subTypes))
        {
            subTypes = new HashSet<RedDotData>();
            dirtyDatas[redDotType] = subTypes;
        }

        if (layers.TryGetValue(redDotType, out var layer))
        {
            foreach (var data in layer.redDotDatas)
            {
                data.isDirty = true;
                subTypes.Add(data);
            }
        }

        CreateUpdateTime();
    }

    private void CreateUpdateTime()
    {
        if (delayTimeUnit.IsRunning)
            return;

        delayTimeUnit = new TimeEvent(delayTime, UpdateMarkRedDot).Publish().timeUnit;
    }

    private void UpdateMarkRedDot()
    {
        UpdateRedValue();

        ChangeRedDotState();

        RemoveOnceData();

        ClearDirtyUnits();
    }

    private void RemoveOnceData()
    {
        foreach (var redDotData in needRemoves)
        {
            if (layers.TryGetValue(redDotData.redDotType, out var layer))
                layer.RemoveData(redDotData);
        }
    }

    private void ChangeRedDotState()
    {
        foreach (var item in needUpdates)
        {
            if (layers.TryGetValue(item.redDotType, out var layer))
            {
                foreach (var redDot in item.redDots)
                    redDot.ChangeRedState(layer.IsRed(item.subType, item.parentType));
            }
        }
    }

    private void UpdateRedValue()
    {
        foreach (var item in dirtyDatas)
        {
            var redDotType = item.Key;
            var redDotDatas = item.Value;

            if (layers.TryGetValue(redDotType, out var layer))
            {
                foreach (var redDotData in redDotDatas)
                {
                    // 当前节点是否激活红点
                    var isRed = layer.RedDotChecker.IsRed(redDotData.subType, redDotData.parentType);
                    if (redDotData.bingType == RedDotBindType.Once && redDotData.isRed && !isRed)
                        needRemoves.Add(redDotData);

                    redDotData.isRed = isRed;
                    redDotData.isDirty = false;
                    needUpdates.Add(redDotData);
                    AddParentToUpdate(layer, redDotData);
                }
            }
        }
    }

    private void AddParentToUpdate(RedDotLayer layer, RedDotData data)
    {
        if (layer.Parent == null)
            return;

        if (layer.Parent.TryGetData(data.parentType, null, out var parentData))
        {
            needUpdates.Add(parentData);
            AddParentToUpdate(layer.Parent, parentData);
        }
    }

    private void ClearDirtyUnits()
    {
        dirtyDatas.Clear();
        needRemoves.Clear();
        needUpdates.Clear();
    }

    public void Reset()
    {
        if (delayTimeUnit.IsRunning)
            delayTimeUnit.Stop();

        ClearDirtyUnits();

        foreach (var unit in layers.Values)
            unit.Reset();
    }
}
